package primitive3D;

import static rayTracer.ApplicationInterface.*;
import static helper.Util.*;

import javax.vecmath.Point3d;
import javax.vecmath.Vector3d;

import rayTracer.AABB;
import rayTracer.Ray;
import rayTracer.Transformation;
import static java.lang.Math.*;

public class Sphere extends Primitive3D {	
	private double radius;

	public Sphere(){
		position = new Point3d(SPHERE_POSITION);
		radius = SPHERE_RADIUS;
		components = null;		
		name = new String("");
		shader = null;
		
		double minX = position.x - radius;
		double minY = position.y - radius;
		double minZ = position.z - radius;
		
		double maxX = position.x + radius;
		double maxY = position.y + radius;
		double maxZ = position.z + radius;
		 
		bb = new AABB( 	new Point3d(minX, minY, minZ), 
						new Point3d(maxX, maxY, maxZ) );
							
}
	
	public Sphere( double radius ) throws Exception{
		this();
		if( radius <= 0 )
			throw new Exception("El radio debe ser un real positivo");
		this.radius = radius;
		double scaleFactor = radius/this.radius;
		this.scale(scaleFactor);
		double minX = position.x - radius;
		double minY = position.y - radius;
		double minZ = position.z - radius;
		
		double maxX = position.x + radius;
		double maxY = position.y + radius;
		double maxZ = position.z + radius;
		 
		bb = new AABB( 	new Point3d(minX, minY, minZ), 
						new Point3d(maxX, maxY, maxZ) );
	}
	
	public Sphere(Point3d center, double radius) throws Exception{
		this(radius);		
		position = new Point3d(center);	
		
		double minX = position.x - radius;
		double minY = position.y - radius;
		double minZ = position.z - radius;
		
		double maxX = position.x + radius;
		double maxY = position.y + radius;
		double maxZ = position.z + radius;
		 
		bb = new AABB( 	new Point3d(minX, minY, minZ), 
						new Point3d(maxX, maxY, maxZ) );
	}
	
	public void scale(double scaleFactor){
		radius *= scaleFactor;
		this.scaleFromReferencePosition(position, scaleFactor);
	}
	
	@Override
	public void setPosition(Point3d otherPosition) {
		bb.min.sub(this.position);
		bb.max.sub(this.position);
		
		bb.min.add(otherPosition);
		bb.max.add(otherPosition);
		position = new Point3d(otherPosition);
	}
	
	@Override
	public Object clone(){		
		Sphere obj=null;
        try{
            obj=(Sphere)super.clone();
        }catch(CloneNotSupportedException ex){
            System.out.println("No se puede duplicar");
        }
        obj.radius = radius;       
        return obj;	    
	}
		
	@Override
	public void scaleFromReferencePosition(Point3d referencePosition, double scaleFactor) {
		// In this case the representation needs only origin and radius. This method does nothing	
		this.scalePointfromReferencePosition(bb.min, referencePosition, scaleFactor);
		this.scalePointfromReferencePosition(bb.max, referencePosition, scaleFactor);
		return;
	}
	
	public String toString(){
		StringBuffer str = new StringBuffer();
		
		//str.append("Sphere \"" + getName() + "\"\nCenter = " + position + "\tRadius = " + radius + "\nColor = " + color);
		str.append("Sphere \"" + getName()+"\" c="+position +"  r="+radius +"  bb.min="+bb.min+"  bb.max="+bb.max );
		return str.toString();
	}
	
	public Point3d getOrigin() {
		return position;
	}
	
	public double getRadius() {
		return radius;
	}

	public double area() {
		return 4*PI*radius*radius;
	}

	// Devuelve la distancia al punto intersectado en el nombre de
	// la funcion. Si no hay interseccion devuelve -1.  
	public double intersects(Ray ray) {
		// vecCO va desde el centro de la esfera hasta el origen del rayo
		Vector3d vecCO = getVector(position, ray.origin);
		
		// Como el vector direccion del rayo esta normalizado queda:
		// t^2 + 2*t*b + c = 0   =>   t = -b +- sqrt( b^2 - c )
		// Entonces, los coeficientes son:
		Vector3d vecDir = ray.vecDir;
		double b = vecDir.dot(vecCO);
		double c = vecCO.dot(vecCO) - radius*radius;
		/*
		// Si el origen del rayo esta afuera de la esfera (c > 0) y el rayo 
		// apunta en sentido contrario a la esfera (b > 0), no hay interseccion
		if( c > 0 && b > 0 ) {
			return -1;
		}
		*/
		// El discriminante me informa si hubo interseccion o no
		double disc = b*b - c;
		
		// No hay soluciones reales, entonces no hay intersecciones
		if( disc < 0 ) {
			return -1;
		}

		// Calculo los puntos de interseccion entre la esfera y el rayo
		if( disc > 0 ) {	// Hay 2 soluciones
			double q = sqrt(disc);
			double k = -b - q;	// Pruebo la menor de las 2 soluciones
			if( k >= 0.001 ) {
				return k;
			}
			// La menor no es >= 0, entonces pruebo la mayor.
			k = -b + q;
			if( k >= 0.001 ){
				return k;
			}
			// Las dos fueron < 0
			return -1;
		}
		
		// disc == 0, hay 1 solucion
		double k = -b;
		if( k >= 0.001 ){
			return k;
		}
		return -1;
	}
	
	// Devuelve un vector normal a la superficie de la esfera que pasa por p
	// p debe ser un punto de la superficie de la esfera
	public Vector3d getNormal(Point3d p) {
		return getVector(position, p);
	}

	@Override
	public int transform(Transformation trans) {		
		trans.translateRotate(position);	
		radius = trans.transformMatrix.getScale()* radius;
		return 0;
	}
	
}
